﻿using Cyss.Core;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace Cyss.Core.Api.JWT
{
    /// <summary>
    /// Token生成类
    /// </summary>
    public class TokenHelper : ITokenHelper
    {
        private readonly JWTConfig _jWTConfig;
        public TokenHelper(JWTConfig jWTConfig)
        {
            _jWTConfig = jWTConfig;
        }

        /// <summary>
        /// 根据一个对象通过反射提供负载生成token
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="user"></param>
        /// <returns></returns>
        public JWTTnToken CreateToken<T>(T user) where T : class
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new List<Claim>();
            //这里我们用反射把model数据提供给它
            foreach (var item in user.GetType().GetProperties())
            {
                if (item.GetCustomAttributes(false).Any(x => x.GetType() == typeof(JWParameterAttribute)))
                {
                    object obj = item.GetValue(user);
                    string value = string.Empty;
                    if (obj != null)
                        value = obj.ToString();


                    claims.Add(new Claim(item.Name, value));
                }

            }
            //创建token
            return CreateTokenString(claims);
        }

        /// <summary>
        /// 根据键值对提供负载生成token
        /// </summary>
        /// <param name="keyValuePairs"></param>
        /// <returns></returns>
        public JWTTnToken CreateToken(Dictionary<string, string> keyValuePairs)
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new List<Claim>();
            //这里我们通过键值对把数据提供给它
            foreach (var item in keyValuePairs)
            {
                claims.Add(new Claim(item.Key, item.Value));
            }
            //创建token
            return CreateTokenString(claims);
        }

        /// <summary>
        /// 根据用户Id和用户名 创建登录token
        /// </summary>
        /// <param name="UserId"></param>
        /// <param name="UserName"></param>
        /// <returns></returns>
        public JWTTnToken CreateToken(string UserId, string UserNo, string UserName)
        {
            Dictionary<string, string> keyValuePairs = new Dictionary<string, string>
                    {
                        { "loginID", UserId.ToString() }, { "loginNo", UserNo.ToString() }, { "loginName", UserName },{"loginDt",DateTime.UtcNow.ToString() },
                    };

            return CreateToken(keyValuePairs);
        }
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims">List的 Claim对象</param>
        /// <returns></returns>
        private JWTTnToken CreateTokenString(List<Claim> claims)
        {
            var now = DateTime.Now;
            var expires = now.Add(TimeSpan.FromMinutes(_jWTConfig.AccessTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: _jWTConfig.Issuer,//Token发布者
                audience: _jWTConfig.Audience,//Token接受者
                claims: claims,//携带的负载
                notBefore: now,//当前时间token生成时间
                expires: expires,//过期时间
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jWTConfig.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));
            return new JWTTnToken { TokenStr = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
        }


        /// <summary>
        /// 验证身份 验证签名的有效性
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        public bool ValiToken(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad = null)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length < 3)//数据格式都不对直接pass
            {
                return false;
            }
            var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
            //配置文件中取出来的签名秘钥
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_jWTConfig.IssuerSigningKey));
            //验证签名是否正确（把用户传递的签名部分取出来和服务器生成的签名匹配即可）
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success)
            {
                return success;//签名不正确直接返回
            }

            //其次验证是否在有效期内（也应该必须）
            var now = ToUnixEpochDate(DateTime.UtcNow);
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

            //不需要自定义验证不传或者传递null即可
            if (validatePayLoad == null)
                return true;

            //再其次 进行自定义的验证
            success = success && validatePayLoad(payLoad);

            return success;
        }
        /// <summary>
        /// 时间转换
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        private long ToUnixEpochDate(DateTime date)
        {
            return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public TokenType ValiTokenState(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action)
        {
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length < 3)//数据格式都不对直接pass
            {
                return TokenType.Fail;
            }
            var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_jWTConfig.IssuerSigningKey));
            //验证签名是否正确（把用户传递的签名部分取出来和服务器生成的签名匹配即可）
            if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))))
            {
                return TokenType.Fail;
            }
            //其次验证是否在有效期内（必须验证）
            var now = ToUnixEpochDate(DateTime.UtcNow);
            if (!(now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())))
            {
                return TokenType.Expired;
            }

            //不需要自定义验证不传或者传递null即可
            if (validatePayLoad == null)
            {
                action(payLoad);
                return TokenType.Ok;
            }
            //再其次 进行自定义的验证
            if (!validatePayLoad(payLoad))
            {
                return TokenType.Fail;
            }
            //可能需要获取jwt摘要里边的数据，封装一下方便使用
            action(payLoad);
            return TokenType.Ok;
        }

        /// <summary>
        /// 从token中获取用户信息
        /// </summary>
        /// <param name="token"></param>
        /// <param name="UserId"></param>
        /// <param name="UserName"></param>
        public T GetTokenUser<T>(string token) where T : class, new()
        {
            var propertyInfos = typeof(T).GetProperties();
            var tokenUser = new T();

            TokenType tokenType = ValiTokenState(token, a => a["iss"] == "WYY" && a["aud"] == "EveryTestOne",
                action =>
                {
                    foreach (var item in propertyInfos)
                    {
                        if (item.GetCustomAttributes(false).Any(x => x.GetType() == typeof(JWParameterAttribute)))
                        {
                            item.SetValue(tokenUser, action[item.Name]);
                        }
                    }
                });
            if (tokenType == TokenType.Ok)
            {
                return tokenUser;
            }
            return null;
        }

        public JWTTnToken CreateToken(string UserId, string UserName)
        {
            throw new NotImplementedException();
        }

        public void GetTokenUser(string token, ref string UserId, ref string UserName)
        {
            throw new NotImplementedException();
        }
    }
}
